<html>
<head>
  <title>19.观察者（Observer）模式</title>
  <basefont face="微软雅黑" size="2" />
  <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  <meta name="exporter-version" content="YXBJ Windows/602628 (zh-CN, DDL); Windows/10.0.0 (Win64); EDAMVersion=V2;"/>
  <style>
    body, td {
      font-family: 微软雅黑;
      font-size: 10pt;
    }
  </style>
</head>
<body>
<a name="25720"/>
<h1>19.观察者（Observer）模式</h1>
<div>
<table bgcolor="#D4DDE5" border="0">
<tr><td><b>创建时间：</b></td><td><i>2020/9/20 5:34</i></td></tr>
<tr><td><b>更新时间：</b></td><td><i>2020/11/8 6:20</i></td></tr>
<tr><td><b>作者：</b></td><td><i>Long Life Student</i></td></tr>
</table>
</div>
<br/>

<div>
<span><div><div><span style="font-weight: bold;">一、基本描述</span></div><ul><li><div>定义对象间的一种一对多的依赖关系，当一个对象的状态发生改变时，所有依赖于它的对象都得到通知并被自动更新。</div></li><li><div>定义解释</div></li><ul><li><div>一对多的关系</div><div>利用观察者模式，主题是具有状态的对象，并且可以控制这些状态。也就是说，有“一个”具有状态的主题。另一方面，观察者使用这些状态，虽然这些状态并不属于它们。有许多的观察者，依赖主题来告诉它们状态何时改变了。这就产生一个关系：“一个”主题对“多个”观察者的关系。</div></li><li><div>依赖</div><div>因为主题是真正拥有数据的人，观察者是主题的依赖者，在数据变化时更新，这样比起让许多对象控制同一份数据来，可以得到更干净的OO设计。</div></li></ul><li><div>又称为：依赖（Dependents）、发布-订阅（Publish-Subscribe）</div></li><li><div>观察者模式可以帮你的对象知悉现状，不会错过该对象感兴趣的事，对象甚至在运行时可决定是否要继续被通知。</div></li><li><div>观察者模式提供了一种对象设计，让主题和观察者之间松耦合，以松耦合的方式在一系列对象之间沟通状态：</div></li><ul><li><div>关于观察者的一切，主题只知道观察者实现了某个接口（也就是Observer接口），主题不需要知道观察者的具体类是谁，做了些什么或其他任何细节。</div></li><li><div>任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表，所以我们可以随时增加观察者。事实上，在运行时我们可以用新的观察者取代现有的观察者，主题不会受到影响。同样的，也可以在任何时候删除某些观察者。</div></li><li><div>有新类型的观察者出现时，主题的代码不需要修改。假如我们有个新的具体类需要当观察者，我们不需要为了兼容新的类型而修改主题代码，所有要做的就是在新的类里实现此观察者的接口，然后注册为观察者即可。主题不在乎别的，它只会发送通知给所有实现了观察者接口的对象。</div></li><li><div>可以独立的复用主题或观察者。如果我们在其他地方需要使用主题或观察者，可以轻易的复用，因为二者并非紧耦合。</div></li><li><div>改变主题或观察者其中一方，并不会影响另一方。因为两者是松耦合的，所以只要他们之间的接口仍被遵守，我们就可以自由的改变它们。</div></li></ul><li><div>将一个系统分割成一系列相互协作的类有一个常见的副作用：需要维护相关对象间的一致性，我们不希望为了维持一致性而使各类紧密耦合，因为这样降低了其可复用性。</div></li><li><div>观察者模式描述了如何建立这种（上一条提到的）关系，这一模式中的关键对象是目标（subject）和观察者（observer）。一个目标可以有任意数目的依赖它的观察者。一旦目标的状态发生改变，所有的观察者都得到通知。作为对这个通知的相应，每个观察者都将查询目标以使其状态与目标的状态同步。</div></li><li><div>这种交互也称为发布-订阅，目标是通知的发布者，它发出通知时并不需要知道谁是它的观察者。可以有任意数目的观察者订阅并接收通知。</div></li><li><div>适用性：</div></li></ul><div style="margin-left: 40px;">1）一个抽象模型有两个方面，其中一个方面依赖于另一方面，将这二者封装在独立的对象中，以使它们可以各自独立的改变和复用。</div><div style="margin-left: 40px;">2）对一个对象的改变需要同时改变其他对象，而不知道具体有多少对象有待改变。</div><div style="margin-left: 40px;">3）一个对象必须通知其他对象，而它又不能假定其他对象是谁。换言之，不希望这些对象是紧密耦合的。</div><div style="margin-left: 40px;">4）跨系统的信息交换场景，如消息队列。</div><div style="margin-left: 40px;">5）需要在系统中创建一个触发链， A对象的行为将影响B对象， B对象的行为将影响C对<span style="font-size: unset; color: unset; font-family: unset;">象……， 可以使用观察者模式创建一种链式触发机制。（但是此触发链不宜过长，见《设计模式之禅》中的建议）</span></div><ul><li><div>观察多个目标。在某些情况下，一个观察者依赖于多个目标可能是由意义的。在这种情况下，必须扩展Update接口以使观察者知道是哪个目标送来的通知。目标对象可以简单的将自己作为Update操作的一个参数，让观察者知道应该去检查哪一个目标。</div></li><li><div>谁触发更新。目标和它的观察者依赖于通知机制来保持一致，但是到底哪一个对象调用Notify来触发更新，有两个选择：</div></li></ul><div style="margin-left: 40px;">1）由目标对象的状态设定操作在改变目标对象的状态后自动调用Notify。这种方法的优点是客户不需要记住要在目标对象上调用Notify，缺点是多个连续的操作会产生多次连续的更新，可能效率较低。</div><div style="margin-left: 40px;">2）让客户负责在适当的时候调用Notify。这样做的优点是客户可以在一系列的状态改变完成后再一次性的触发更新，避免了不必要的中间更新，缺点是给客户增加了触发更新的责任，由于客户可能会忘记调用Notify，这种方式容易出错。</div><ul><li><div>避免特定于观察者的更新协议——推/拉模型。</div></li></ul><div style="margin-left: 40px;">1）推模型（push model）：一个极端模型，目标向观察者发送关于改变的详细信息，而不管它们需要与否。</div><div style="margin-left: 40px;">2）拉模型（pull model）：一个极端模型，目标除最小通知外什么也送不出，而在此之后由观察者显式的向目标询问细节。</div><div style="margin-left: 40px;">3）拉模型强调的是目标不知道它的观察者，而推模型假定目标知道一些观察者的需要的信息。推模型可能使得观察者相对难以复用，因为目标对观察者的假定可能并不总是正确的。另一方面，拉模型可能效率较差，因为观察者对象需在没有目标对象帮助的情况下确定什么改变了。</div><ul><li><div>显式的指定感兴趣的改变。可以扩展目标的注册接口，让各观察者注册为仅对特定事件感兴趣，以提高更新的效率。当一个事件发生时，目标仅通知那些已注册为对该事件感兴趣的观察者。支持这种做法的一种途径是，使用目标对象的<span style="font-weight: bold;">方面（aspect）</span>的概念。可用如下代码将观察者对象注册为对目标对象的某特定事件感兴趣：</div></li></ul><div>            void Subject::Attach(Observer*, Aspect&amp; interest);</div><div>            此处interest指定感兴趣的事件。在通知时刻，目标将这方面的改变作为Update操作的一个参数提供给它的观察者，例如：</div><div>            void Observer::Update(Subject*, Aspect&amp; interest);</div><div>            个人理解，这里的方面指的其实是对象当中的一部分成员，比如上例中，方面就是目标对象当中的部分事件（成员变量）。</div><ul><li><div>观察者模式所做的工作其实就是在解除耦合，让耦合的双方都依赖于抽象，而不是依赖于具体，从而使得各自的变化都不会影响另一边的变化。</div></li><li><div>观察者模式在Java语言中地位非常重要，在JDK的java.util包中，针对观察者模式提供了两个基础设施：</div></li><ul><li><div>java.util.Observable：可扩展父类，对应于Subject；</div></li><li><div>java.util.Observer：接口，对应Observer。</div></li></ul></ul><div style="margin-left: 40px;">这两个类使用方法如下：</div><ul><ul><li><div>可以使用推或者拉的方式传递数据</div></li><li><div>Observable 提供 addObserver() 和 deleteObserver() 方法，增加和删除观察者</div></li><li><div>发送通知的步骤：</div></li><ul><li><div>在可观察者类中，先调用 setChanged() 方法，标记状态已经改变的事实</div></li><li><div>调用两种 notifyObserver() 方法中的一个：</div></li><ul><li><div>notifyObservers()</div></li><li><div>notifyObservers(Object arg)：其中，arg是一个数据对象</div></li></ul></ul><li><div>接收通知：</div></li><ul><li><div>在 Observer 接口中，实现了更新方法：</div><div>update(Observable o, Object arg)</div><div>主题本身当作第一个变量，好让观察者知道是哪个主题通知它的</div><div>arg是上述 notifyObserver(Object arg) 的参数，是一个数据对象，用于推送数据，如果没有说明则为空</div></li></ul><li><div>如果希望“推”数据给观察者，可以把数据对象传送给 notifyObservers(Object arg) 方法，否则，观察者就必须从可观察者对象中“拉”数据</div></li><li><div>setChanged() 方法用来标记状态已经改变的事实，好让 notifyObservers() 知道当它被调用时应该更新观察者。如果调用 notifyObserver() 之前没有先调用 setChanged()，观察者就不会被通知。</div><div>这样做有其必要性，setChanged() 方法可以让你在更新观察者时，有更多的弹性，可以更适当的通知观察者。</div><div>相关的方法有：</div></li><ul><li><div>setChanged()：设置 changed 状态标志位true</div></li><li><div>clearChanged()：将 changed 状态设置回false</div></li><li><div>hasChanged()：获取 changed 标志的当前状态</div></li></ul></ul><li><div>观察者模式是使用频率最高的设计模式之一。它为实现对象之间的联动提供了一套完整的解决方案， 凡是涉及到<span style="font-size: unset; color: unset; font-family: unset;">一对一或者一对多的对象交互场景都可以使用观察者模式。</span></div></li><li><div>观察者模式描述了如何建立对象与对象之间的依赖关系，以及如何构造满足这种需求的系统。</div></li><li><div>在观察者模式当中，代码不应当依赖主题通知观察者对象的顺序（特定的通知顺序），因为一旦观察者/主题的实现有所改变，通知次序就会改变，很可能会产生错误的结果。</div></li><li><div>对设计原则的支持</div></li><ul><li><div>找出程序中会变化的方面，然后将其和固定不变的方面相分离</div><div>在观察者模式中，会改变的是主题的状态，以及观察者的数目和类型，用这个模式可以改变依赖于主题状态的对象，却不必改变主题，这就叫提前规划。</div></li><li><div>依赖倒转原则：针对接口编程，不针对实现编程</div><div>主题与观察者都使用接口，观察者利用主题的接口向主题注册，而主题利用观察者接口通知观察者。这样可以让两者之间运作正常，又同时具有松耦合的优点。</div></li><li><div>合成复用原则：多用组合，少用继承</div><div>观察者模式利用“组合”将许多观察者组合进主题类中。对象之间的这种关系不是通过继承产生的，而是在运行时利用组合的方式而产生的。</div></li></ul><li><div>一些实际应用：</div></li><ul><li><div>在JDK中，事件处理模型采用基于观察者模式的委派事件模型（Delegation Event Model，DEM），即一个Java组件所引发的事件并不由引发事件的对象自己来负责处理，而是委派给独立的事件处理对象负责。</div></li><li><div>在当前流行的MVC(Model-View-Controller)架构中也应用了观察者模式， MVC是一种架构模<span style="font-size: unset; color: unset; font-family: unset;">式， 它包含三个角色： 模型(Model)， 视图(View)和控制器(Controller)。 其中模型可对应于观察</span><span style="font-size: unset; color: unset; font-family: unset;">者模式中的观察目标， 而视图对应于观察者， 控制器可充当两者之间的中介者。 当模型层的</span><span style="font-size: unset; color: unset; font-family: unset;">数据发生改变时， 视图层将自动改变其显示内容。</span></div></li><li><div>Java当中，Swing大量使用观察者模式，许多GUI框架也是如此。</div></li><li><div>在JavaBeans和RMI当中也可以被应用。</div></li></ul><li><div>观察者模式的应用：</div></li><ul><li><div>各种编程语言的GUI事件处理</div></li><li><div>基于事件的XML解析技术（ 如SAX2）</div></li><li><div>Web事件处理</div></li><li><div>观察者模式是JDK中使用最多的模式之一，非常有用。</div></li></ul></ul><div><span style="font-weight: bold;">二、图例</span></div><div style="text-align: center;"><img src="19.观察者（Observer）模式_files/Image.png" type="image/png" data-filename="Image.png"/></div><ul><li><div><span style="font-weight: bold;">Subject（目标）：</span>目标知道它的观察者，可以有任意多个观察者观察同一个目标。提供注册和删除观察者对象的接口，这里的对象是观察者基类对象。也称为主题，抽象通知者，用一个抽象类或一个接口实现。定义目标必须实现的职责，仅仅完成作为目标必须实现的职责：管理观察者并通知观察者（也就是说最好不要涉及具体业务）。职责非常简单，就是定义谁能够观察，谁不能够观察。把所有对观察者对象的引用保存在一个聚类里，每个目标都可以有任何数量的观察者，抽象主题提供一个接口，可以增加和删除观察者对象。</div></li><li><div><span style="font-weight: bold;">Observer（观察者）：</span>为那些在目标发生改变时需获得通知的对象定义一个更新接口<span style="font-size: unset; color: unset; font-family: unset;">。抽象观察者，为所有的具体观察者类定义一个接口，在得到主题的通知时更新自己。接收到消息后调用update操作，对接收到的信息进行处理。</span></div></li><li><div><span style="font-weight: bold;">ConcreteSubject（具体目标）：</span>将有关状态存入各ConcreteObserver对象，当它的状态发生改变时，向它的各个观察者发出通知。定义自己的业务逻辑，同时定义对那些事件进行通知，通常包含有经常发生改变的成员，当它发生改变时，向各个观察者发出通知，如无需扩展目标类，具体目标类可以省略。</div></li><li><div><span style="font-weight: bold;">ConcreteObserver（具体观察者）：</span>维护一个指向ConcreteSubject对象的引用。存储有关状态，这些状态应与目标的状态保持一致。实现Observer的更新接口以使自身状态与目标状态保持一致。这里的对象是具体的目标对象，每个观察者接收消息后有自己的处理逻辑。</div></li><li><div><span style="font-weight: bold;">协作：</span></div></li></ul><div style="margin-left: 40px;">1）当ConcreteSubject发生任何可能导致其观察者与其本身状态不一致的改变时，它将通知它的各个观察者。</div><div style="margin-left: 40px;">2）在得到一个具体目标的改变通知后，ConcreteObserver对象可向目标对象查询信息，ConcreteObserver使用这些信息以使它的状态与目标对象的状态一致。</div><div style="margin-left: 40px;">3）有多个Observer对象，其中一个Observer对象向ConcreteSubject对象发出改变状态的请求，需要注意发出改变请求的Observer对象本身状态并不立即更新，而是将状态改变推迟到它从目标得到一个通知之后。</div><div style="margin-left: 40px;">4）通知notify不总是由目标对象调用，也可以被一个观察者或其他对象调用。</div><div><span style="font-weight: bold;">三、优缺点</span></div><div><span style="font-weight: bold;">优点</span></div><ul><li><div><span style="font-size: unset; color: unset; font-family: unset; font-weight: bold;">目标和观察者之间的抽象耦合</span><span style="font-size: unset; color: unset; font-family: unset;">。一个目标所知道的仅仅是它由一系列观察者，每个都符合抽象的Observer类的简单接口。目标不知道任何一个观察者属于哪一个具体的类。这样目标和观察者之间的耦合是抽象的和最小的。</span><span style="font-size: unset; color: unset; font-family: unset;">观察者模式允许独立的改变目标和观察者。可以单独复用目标对象而无需同时复用其观察者，反之亦然。它也使你可以在不改动目标和其他观察者的前提下增加观察者。</span>由于观察目标和观察者没有紧密地耦合在一起， 因<span style="font-size: unset; color: unset; font-family: unset;">此它们可以属于不同的抽象化层次。</span></div></li><li><div><span style="font-weight: bold;">支持广播通信</span>。不像通常的请求，目标发送的通知不需指定它的接收者。通知被自动广播给所有已向该目标登记的有关对象。目标对象并不关心到底有多少对象对自己感兴趣，它唯一的责任就是通知它的各观察者。这给了在任何时刻增加和删除观察者的自由。处理还是忽略一个通知取决于观察者。</div></li><li><div><span style="font-weight: bold;">建立一套触发机制</span>。根据单一职责原则，每个类的职责是单一的，观察者模式可以完美的实现这了的触发链，将各个职责单一的职责链串联成复杂的逻辑关系。</div></li><li><div>观察者模式可以实现表示层和数据逻辑层的分离， 定义了稳定的消息更新传递机制， 并抽<span style="font-size: unset; color: unset; font-family: unset;">象了更新接口， 使得可以有各种各样不同的表示层充当具体观察者角色。</span></div></li><li><div>观察者模式满足“开闭原则”的要求， 增加新的具体观察者无须修改原有系统代码， 在具体<span style="font-size: unset; color: unset; font-family: unset;">观察者与观察目标之间不存在关联关系的情况下， 增加新的观察目标也很方便。</span><span style="font-size: unset; color: unset; font-family: unset;">无论观察者还是目标都非常容易扩展。</span></div></li></ul><div><span style="font-weight: bold;">缺点</span></div><ul><li><div><span style="font-size: unset; color: unset; font-family: unset;"><span style="font-size: unset; color: unset; font-family: unset; font-weight: bold;">意外的更新</span>。因为一个观察者并不知道其他观察者的存在，它可能对改变目标的最终代价一无所知。在目标上一个看似无害的操作可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新。此外，如果依赖准则的定义或维护不当，常常会引起错误的更新，这种错误通常很难捕捉。简单的更新协议不提供具体细节说明目标中什么被改变了，这就使得上述问题更加严重，如果没有其他协议帮助观察者发现什么发生了改变，它们可能会被迫尽力减少改变。</span></div></li><li><div>尽管已经使用了依赖倒转原则，但是“抽象通知者”还是依赖“抽象观察者”，也就是说，万一没有了抽象观察者这样的接口，通知的功能就完成不了。</div></li><li><div><span style="font-weight: bold;">需要考虑开发效率</span>。一个目标，多个观察者，开发和调试比较复杂。</div></li><li><div><span style="font-weight: bold;">需要考虑运行效率</span>。如果一个观察目标对象有很多直接和间接的观察者，将所有的观察者都通知到会花费很多时间。并且应考虑异步通知的方式，否则如果通知顺序执行，一个观察者卡克，会影响整体运行效率。多级触发时的效率更令人担忧。</div></li><li><div>如果在观察者和观察目标之间存在循环依赖，观察目标可能会触发它们之间进行循环调用，可能导致系统崩溃。</div></li></ul><div><span style="font-weight: bold;">四、可能的改进</span></div><ul><li><div>可以利用委托改进观察者模式。</div></li><li><div>观察者和目标之间的消息沟通。目标状态改变会触发观察者的一个行为，同时会传递一个消息给观察者。实际中，观察者的Update方法接受两个参数：一个是目标对象，一个是DTO（Data Transfer Object，数据传输对象）</div></li><li><div>需要解决观察者如何快速响应的问题：一是采用多线程技术的异步通信架构，二是采用缓存技术。</div></li></ul><div><span style="font-weight: bold;">五、个人理解</span></div><ul><li><div>观察者模式的思路及实现并不难，在实际中应用非常广泛，实际上其思路在宏观的架构设计上也有很多应用。</div></li><li><div>核心思路是解耦。</div></li></ul><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div><div><br clear="none"/></div></div><div><br/></div></span>
</div></body></html> 